package treeedit;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.util.Enumeration;
import java.util.EventObject;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.CellEditorListener;
import javax.swing.plaf.metal.MetalIconFactory.TreeControlIcon;
import javax.swing.tree.DefaultTreeCellEditor;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreePath;

import com.sun.corba.se.spi.legacy.connection.GetEndPointInfoAgainException;


public class CodeEditor extends JTextField implements TreeCellEditor,KeyListener
{
	private JTree tree;
	private CodeModel model;
	private CodeNode current;
	private String val;
	private CodeFiller codeFiller = new CodeFiller();
	
	public CodeEditor()
	{
		//addActionListener(this);
		addKeyListener(this);
		
	}
	
	public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row)
	{
		CodeNode node = (CodeNode) value;
		if(node.getRepresentation().equals("( )"))
		{
			setText("");
		}
		else
		{
			setText(node.getRepresentation());
		}
		setPreferredSize(new Dimension(1000,20));
		
		this.tree = tree;
		this.model = (CodeModel)tree.getModel();
		
		return this;
	}
	
	

	public void addCellEditorListener(CellEditorListener l) {
		//System.out.println("addCellEditorListener");
	}

	public void cancelCellEditing() 
	{
		/*System.out.println("cancel " + tree);
		System.out.println("Path " + tree.getSelectionPath());
		*/
	}

	//TODO Absolulty Dramaticly tragic solution 
	public Object getCellEditorValue() {
		//System.out.println("getCellEditorValue");
		//TreePath path = tree.getEditingPath();
		if(tree.getEditingPath() == null) return getText();
		CodeNode node = ((CodeNode)tree.getEditingPath().getLastPathComponent());
		if(val != null )
			node.setRepresentation(val);
		else
			node.setRepresentation(getText());
		val = null;
	
		
		return getText();
	}

	public boolean isCellEditable(EventObject e) {
		//System.out.println("EDITABLE??");
		if(e==null) return true;
	//	System.out.println("PAST");
		if(e instanceof MouseEvent)
		{
			MouseEvent m = (MouseEvent)e;
			tree = (JTree)e.getSource();
			TreePath path = tree.getClosestPathForLocation(m.getX(), m.getY());
			CodeNode node = (CodeNode) path.getLastPathComponent();
			if(!isNodeEditable(node)) return false;
			
			if(m.getClickCount() < 2) return false;
			
		}
		return true;
		}

	public void removeCellEditorListener(CellEditorListener l)
	{
		//System.out.println("remove listener");
	}

	public boolean shouldSelectCell(EventObject e) {
		return false;
	}

	public boolean stopCellEditing() {
//		/System.out.println("stopCellEditing");
		return true;
	}


	// TODO doubtfull this is the right way to solve the problem
	public void setNodeToEditorValue()
	{
		//Corrected to some extend
	}

	private void reactInsertNext ( KeyEvent k)
	{
		Enumeration enumeration = TreeStateController.saveExpansionState(tree);
		
//		If code filler active update string
		String str = codeFiller.finish();
		if(!str.equals(""))
		{
			setText(str);
		}
		else 
		//No allowed to press space twice
		if(getText().equals(""))
		{	
			k.consume();
			return ;
		}
		
		
		
		//Adding new brother node
		TreePath t = ((CodeModel)tree.getModel()).addNewBrotherNode(tree, tree.getEditingPath());
		
		//Disabling code filler
		codeFiller.setVisible(false);
		k.consume();
		tree.stopEditing();
		//Expanding new path
		if(TreeStateController.isValidPath(tree,t))
			tree.expandPath(t);
		//Loading previous expansion state
		TreeStateController.loadExpansionState(tree, enumeration);
		//Jumping to the newly created path
		tree.startEditingAtPath(t);
		
		
	}
	
	private void reactShowFiller(KeyEvent k)
	{
		codeFiller.attachTo((Component)k.getSource());
		codeFiller.filter(getText());
		codeFiller.setVisible(true);
		k.consume();
	}
	
	public void reachShowSense( Component c)
	{
		codeFiller.attachTo(c);
		codeFiller.filter(getText());
		codeFiller.setVisible(true);
	}
	
	private void reactScrollFillerDown(KeyEvent k)
	{
		codeFiller.selectDown();
		k.consume();
	}
	
	private void reactScrollFillerUp(KeyEvent k)
	{
		codeFiller.selectUp();
		k.consume();
	}
		
	private void reactConfirm ( KeyEvent k)
	{
		Enumeration enumeration = TreeStateController.saveExpansionState(tree);
		
		//If filler is visible accept its choice
		if(codeFiller.isVisible() && !codeFiller.finish().equals(""))
		{
			setText(codeFiller.finish());
			codeFiller.setVisible(false);
		}
		//Get ediitng path
		TreePath path = tree.getEditingPath();

		//Debuging purpuses should never happen
		if(path == null) return ;

		tree.stopEditing();
		tree.cancelEditing();

		TreePath selPath = path;
		//Deleting node if no text was entered
		if(getText().length() == 0)
		{
			selPath =getBestNextPath(path);
		//	System.out.println("BEST PATH " + selPath);
			model.deleteNode(tree, path);
			
		}
		else 
		{
			//System.out.println("BEFORE SEL");
			CodeNode node = (CodeNode)path.getLastPathComponent();
		//	System.out.println("NODE CODE " + node.getCode() + " is function " + node.getParent().isFunction());
			if(node.getParent().isFunction() && node.getParent().getChildren().size() == 1)
			{
				System.out.println("IN SEL");
				
				selPath = path.getParentPath();
			}
				
		
		}
		
		//Restoring tree state
		TreeStateController.loadExpansionState(tree, enumeration);
	
		if(TreeStateController.isValidPath(tree,selPath))
			tree.setSelectionPath(selPath);
		k.consume();
		return ;		

	}
	
	public void keyPressed(KeyEvent k) {
		//System.out.println("PRESSED TEXT " + getText());
		int code = k.getKeyCode();
		current = (CodeNode)tree.getEditingPath().getLastPathComponent();

			
		if(!current.isString())
		{
			if(code == KeyEvent.VK_SPACE && k.isControlDown())
			{ //Generate filler
				reactShowFiller(k);
				Lisper.getInstance().checkDefuns((CodeNode)model.getRoot());
				return ;
			
			}
			else if(code == KeyEvent.VK_SPACE)
			{
				reactInsertNext(k);
				Lisper.getInstance().checkDefuns((CodeNode)model.getRoot());
				return ;
				
			}

			else if(code == KeyEvent.VK_DOWN)
			{
				reactScrollFillerDown(k);
				Lisper.getInstance().checkDefuns((CodeNode)model.getRoot());
				return ;
				
			}
			else if(code == KeyEvent.VK_UP)
			{
				reactScrollFillerUp(k);
				Lisper.getInstance().checkDefuns((CodeNode)model.getRoot());
				return ;
			
			}
			else if(code == KeyEvent.VK_BACK_SPACE && getText().equals(""))
			{
				reactBackSpaceAndTextEmpty(k);
				Lisper.getInstance().checkDefuns((CodeNode)model.getRoot());
				return ;
			
			}
			
		}
		
		if(code == KeyEvent.VK_ENTER || code == KeyEvent.VK_ESCAPE || ( code == KeyEvent.VK_C && k.isControlDown()) )
		{
			if(current.isString()) setText(getText());
			reactConfirm(k);
			Lisper.getInstance().checkDefuns((CodeNode)model.getRoot());
			return ;
		}

	}

	private void reactQuote(KeyEvent k)
	{
		Enumeration enumeration = TreeStateController.saveExpansionState(tree);
		TreePath t = tree.getEditingPath();
		
		//If is not in qoute mode go into it
		if(!current.isString()) 
		{
			current.setString(true);
			setText("");
			setForeground(Color.CYAN);
			k.consume();
			return;
		}
		else //If ending quote mode
		{
			k.consume();
			//setText(getText() + "\"");
			
			tree.stopEditing();
			if(TreeStateController.isValidPath(tree,t))
				tree.setSelectionPath(t);
						
			TreeStateController.loadExpansionState(tree, enumeration);
		
		}
	}
	
	private static boolean isBegining(String str)
	{
		if(str.equals("") || str.equals("'") || str.equals("`") || str.equals("#'") || str.equals("#`"))
		{
			return true;
		}
		return false;
	}
	
	private void reactConfirmAndInsertChild( KeyEvent k)
	{
		//If inserting into unempty field
		Enumeration enumeration = TreeStateController.saveExpansionState(tree);
		TreePath t ;

		TreePath b = null;
		
		if(getText().length() != 0 && !isBegining(getText()))
		{
			t = model.addNewBrotherNode(tree, tree.getEditingPath());
			((CodeNode)t.getLastPathComponent()).setCode("( )");
			t = ((CodeModel)tree.getModel()).addNewChildNode(tree, t);
			tree.stopEditing();
			tree.cancelEditing();
		}
		else
		{
			//System.out.println("Editing path after " + tree.getEditingPath());
			setText(getText() + "( )");
			
			b = tree.getEditingPath();
			
			tree.stopEditing();
			tree.cancelEditing();
			t = ((CodeModel)tree.getModel()).addNewChildNode(tree, b);
		}

		k.consume();
//		System.err.println("B PATH: " + b);
//		System.err.println("T PATH: " + t);
		if(TreeStateController.isValidPath(tree,t))
			tree.expandPath(t);
		
		TreeStateController.loadExpansionState(tree, enumeration);
		if(TreeStateController.isValidPath(tree,t))
				tree.startEditingAtPath(t);
	
	}
	
	private void reactBackSpaceAndTextEmpty(KeyEvent k)
	{
		Enumeration enumeration = TreeStateController.saveExpansionState(tree);

		TreePath path = tree.getEditingPath();
		if(path == null) return;

		CodeNode thisNode = (CodeNode)path.getLastPathComponent();
		CodeNode up = thisNode.getParent().getPrevious(thisNode);
		CodeNode down = thisNode.getParent().getNext(thisNode);
		
		tree.cancelEditing();
		
		model.deleteNode(tree, path);
		TreeStateController.loadExpansionState(tree,enumeration);
				
		
		if(down != null)
		{
			tree.setSelectionPath(path.getParentPath().pathByAddingChild(down));
		}
		else if(up != null)
		{
			tree.setSelectionPath(path.getParentPath().pathByAddingChild(up));
		}
		else
		{
			tree.setSelectionPath(path.getParentPath());
		}
		k.consume();
		return;
	}
	
	private void reactOtherKey(KeyEvent k)
	{
		char c = k.getKeyChar();
		
		//We handle space in key pressed
		if(c == KeyEvent.VK_SPACE) 
		{
				k.consume();
				return ;
		}
	
		//Handling highlighting key words
		String str;
		
		/*if(!Character.isLetterOrDigit( k.getKeyChar()) 
				&& Character.getType(k.getKeyChar()) != Character.MATH_SYMBOL) str = getText();
		else  str = getText() + k.getKeyChar();
		*/
		//If printable
		if(c >= 31 && c < 127 )str = getText() + k.getKeyChar(); else  str = getText();
		
		codeFiller.filter(str);
		//System.out.println("Keyword " + str);
		if(Lisper.getInstance().isFunction(str))
		{
//			System.out.println("IS FUNCTION");
			setForeground(Color.RED);
		}
		else setForeground(Color.BLACK);
		return;

	}
	
	private void reactCloseAndInsertUncle( KeyEvent k)
	{
		Enumeration enumeration = TreeStateController.saveExpansionState(tree);
		TreePath t ;
		
		TreePath path = tree.getEditingPath();
		if(path == null) return ;
		
		CodeNode node = (CodeNode) path.getLastPathComponent();
		CodeNode parent = node.getParent();
		if(parent == null) return;
		
		if(!parent.regress(node))
			t = ((CodeModel)tree.getModel()).addNewClosingNode(tree, tree.getEditingPath());
		else
		{
		//	System.out.println("REGRESSED");
			tree.stopEditing();

			tree.getModel().valueForPathChanged(new TreePath(tree.getModel().getRoot()),"");

			TreeStateController.loadExpansionState(tree, enumeration);
		
			k.consume();
			return;
		}
		
		
		tree.stopEditing();
		
		if(getText().length() == 0)
		{
//			System.out.println("Deleted");
			model.deleteNode(tree, path);
		}
		
		TreeStateController.loadExpansionState(tree, enumeration);
		k.consume();
		if(TreeStateController.isValidPath(tree,t))
			tree.startEditingAtPath(t);
	}
	
	public void keyTyped(KeyEvent k) {
	
		//System.out.println("KeyCode " + k.getKeyCode());
//		System.out.println("KeyTyped " + k.getKeyChar());
//		System.out.println("KeyTyped " + getText());
//		
		Enumeration enumeration = TreeStateController.saveExpansionState(tree);
		current = (CodeNode)tree.getEditingPath().getLastPathComponent();


		
		char c = k.getKeyChar();
		CodeModel cm = (CodeModel)tree.getModel();

		TreePath t = null;
		
		if(c == '\"')
		{
			reactQuote(k);
		}
		
		if ( current.isString() ) // If in quote mode no event will be handled 
		{
			return ;
		}
		else if(c == '(')
		{
			reactConfirmAndInsertChild(k);
			
		}	
		else if(c == ')')
		{
				reactCloseAndInsertUncle(k);
				
		}
		else//Highliting keyword event
		{
			reactOtherKey(k);
			
		}

		Lisper.getInstance().checkDefuns((CodeNode)model.getRoot());
		return ;
	}

	private TreePath getBestNextPath( TreePath path)
	{
		//Debuging event
		if(path == null) return null;
		
		//If root return root path
		if(path.getPathCount() == 1) return path;
	
		CodeNode node = (CodeNode)path.getLastPathComponent();
		CodeNode parent = (CodeNode)node.getParent();
		CodeNode n;
		//If no children return parent path
		if(parent.getChildren().size() == 0) return path.getParentPath();
	
		//Next node down
		if((n = parent.getNext(node)) != null)
		{
			return path.getParentPath().pathByAddingChild(n);
		}
		
		//	Next node up
		if((n = parent.getPrevious(node)) != null)
		{
			return path.getParentPath().pathByAddingChild(n);
		}
		
		//If all other failed
		return path.getParentPath();
	
	}
	
	public static boolean isNodeEditable(CodeNode node)
	{
		if((node.getRepresentation().equals("( )")
				|| node.getRepresentation().equals("'( )")
				|| node.getRepresentation().equals("`( )")
				|| node.getRepresentation().equals("#'( )")
				|| node.getRepresentation().equals("#`( )")
				)&& node.getChildren().size() != 0) return false;
		return true;
	}
	
	public void keyReleased(KeyEvent k) {
		//k.consume();
		// TODO Auto-generated method stub
		
	}

}
